﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using Complete.Data.Support;
using Complete.Data.Linq;
using Complete.Data.Linq.Expressions;

namespace Complete.Data.Internal
{
	internal class DistinctColumnExtractor : CompleteExpressionVisitor
	{
		private List<Expression> _Columns = new List<Expression>();
		private INameMapper _Mapper;

		private DistinctColumnExtractor(INameMapper mapper)
		{
			_Mapper = mapper;
		}

		public static IEnumerable<Expression> GetDistinctColumns(INameMapper mapper, Expression expr)
		{
			var dce = new DistinctColumnExtractor(mapper);
			dce.Visit(expr);

			if (dce._Columns.Count == 0)
				throw new InvalidOperationException("Unable to find any sort/equality columns in this expression.");

			return dce._Columns.ToReadOnly();
		}

		protected override Expression Visit(Expression exp)
		{
			if (!exp.Type.IsComplexType())
			{
				_Columns.Add(exp);
				return exp;
			}
			return base.Visit(exp);
		}

		protected override Expression VisitConstant(ConstantExpression c)
		{
			var type = c.Type;
			if (!type.IsComplexType())
				_Columns.Add(c);
			else
			{
				var columns = _Mapper.CanHandleType(type) ?
					_Mapper.GetPrimaryKeyColumns(type) :
					DefaultNameMapper.GetColumnsForType(type);
				var values = columns.Select(cm =>
					{
						var pi = cm.MemberInfo;
						var v = pi.GetValue(c.Value, null);
						return Expression.Constant(v);
					});
				_Columns.AddRange(values);
			}
			return c;
		}

		protected override Expression VisitMemberAccess(MemberExpression m)
		{
			HandleColumn(m);
			return m;
		}

		protected override System.Linq.Expressions.Expression VisitParameter(ParameterExpression p)
		{
			HandleColumn(p);
			return p;
		}

		private void HandleColumn(Expression expr)
		{
			var type = expr.Type;
			if (!type.IsComplexType())
				_Columns.Add(expr);
			else
				HandleComplexColumn(expr);
		}

		private void HandleComplexColumn(Expression expr)
		{
			var type = expr.Type;
			var columns = _Mapper.CanHandleType(type) ?
				_Mapper.GetPrimaryKeyColumns(type) :
				DefaultNameMapper.GetColumnsForType(type);
			_Columns.AddRange(columns.Select(cm => Expression.MakeMemberAccess(expr, cm.MemberInfo)));
		}
	}
}
